package me.chyxion.spring.ext.codegen.services;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilderFactory;

import me.chyxion.dao.utils.DbTool;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.w3c.dom.Element;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

import freemarker.template.Template;

/**
 * @version 0.0.1
 * @since 0.0.1
 * @author chyxion <br />
 * chyxion@163.com <br />
 * Dec 3, 2014 11:25:17 AM
 */
@Service
public class CodeGenBaseTool {
	private static final Logger log = LoggerFactory.getLogger(CodeGenBaseTool.class);

	@Autowired
	private FreeMarkerConfigurer fmCfg;
	@Autowired(required = false)
	private JdbcTemplate jdbcTpl;
	@Resource(name = "projectDir")
	private String projDir;
	private String groupId;

	/**
	 * @return the projDir
	 */
	public String getProjDir() {
		return projDir;
	}

	/**
	 * @return the groupId
	 */
	public String getGroupId() {
		return groupId;
	}

	/**
	 * save record to file
	 * @param module
	 * @param model
	 * @param files
	 */
	public void saveRecord(Map<String, Object> mapRec) {
    	mapRec.put("date_created", new Date());
    	writeStore(addRec(mapRec));
	}

	/**
	 * exec SQL file
	 * @param sqlFile
	 */
	public void execSQL(File sqlFile) {
		if (jdbcTpl != null) {
			DbTool.executeSqlScript(jdbcTpl, new FileSystemResource(sqlFile), false);
		}
	}

	/**
	 * exec SQL string
	 * @param sql
	 */
	public void execSQL(String sql) {
		if (jdbcTpl != null) {
			jdbcTpl.execute(sql);
		}
	}

	/**
	 * init config
	 */
	@PostConstruct
	public void init() {
		// disable cache
		log.info("Disable FreeMarker Cache In Dev Mode.");
		fmCfg.getConfiguration().setTemplateUpdateDelay(0);
		try {
			log.info("Parse Group ID From [{}/pom.xml].", projDir);
			Element root = DocumentBuilderFactory.newInstance()
				.newDocumentBuilder()
				.parse(new File(projDir, "pom.xml"))
				.getDocumentElement();
			root.normalize();
			groupId = root.getElementsByTagName("groupId").item(0).getTextContent().trim();
			log.info("Group ID [{}] Found.", groupId);
		}
		catch (Exception e) {
			throw new RuntimeException("Parse [groupId] From Maven [pom.xml] File ERROR Caused.", e);
		}
	}

	/**
	 * list all models generated
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<Map<String, Object>> listAll() {
		return (List<Map<String, Object>>) getStore().get("items");
	}

	/**
	 * get store data
	 * @return
	 */
	private Map<String, Object> getStore() {
    	File file = getStoreFile();
    	Map<String, Object> mapData = null;
    	if (file.exists()) {
    		FileInputStream fin = null;
    		try {
				fin = new FileInputStream(file);
				mapData = JSON.parseObject(IOUtils.toString(fin, CharEncoding.UTF_8));
			}
			catch (IOException e) {
				log.info("Parse JSON File [{}] ERROR Caused.", file.getName());
				try {
					String backupFileName = 
							"codegen/data_broken_" + 
							DateFormatUtils.format(new Date(), "yyyy_MM_dd_HH_mm_ss") + 
							".json";
					log.info("Backup Data File To [{}].", backupFileName);
					if (fin != null) {
						// backup data file
						IOUtils.copy(fin, 
							new FileWriter(new File(projDir, backupFileName)),
							CharEncoding.UTF_8);
					}
				}
				catch (IOException e1) {
					log.info("Backup JSON File [{}] ERROR Caused.", file.getName());
				}
			}
    		finally {
    			IOUtils.closeQuietly(fin);
    		}
    	}
    	if (mapData == null) {
    		mapData = new HashMap<String, Object>();
    		mapData.put("items", new LinkedList<Object>());
    	}
		return mapData;
	}

	private void writeStore(Map<String, Object> storeData) {
    	File file = getStoreFile();
		try {
			storeData.put("date_updated", new Date());
			FileUtils.write(file, JSON.toJSONStringWithDateFormat(storeData, 
					"yyyy-MM-dd HH:mm:ss", 
					SerializerFeature.PrettyFormat), 
				CharEncoding.UTF_8);
		}
		catch (IOException e) {
			throw new RuntimeException("Write Data To File [" + file.getAbsolutePath() + "] ERROR Caused.", e);
		}
	}

	private File getStoreFile() {
    	return new File(projDir, ".codegen/data.json");
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, Object> addRec(Map<String, Object> mapRec) {
		Map<String, Object> mapData = getStore();
		Map<String, Object> rec = 
			findRec(mapData, (String) mapRec.get("module"), 
				(String) mapRec.get("model"));
		if (rec != null) {
			rec.putAll(mapRec);
		}
		else {
			((List<Map<String, Object>>) mapData.get("items")).add(mapRec);
		}
		return mapData;
	}

	@SuppressWarnings("unchecked")
	private Map<String, Object> findRec(Map<String, Object> store, String module, String model) {
		for (Map<String, Object> item : (List<Map<String, Object>>) store.get("items")) {
    	    if (eqauls(item, module, model)) {
				return item;
			}
		}
		return null;
	}

	/**
	 * delete rec
	 * @param module
	 * @param model
	 */
	@SuppressWarnings("unchecked")
	public void deleteRec(String module, String model, boolean dropTable) {
    	Map<String, Object> mapData = getStore();
		Iterator<Map<String, Object>> it = 
			((List<Map<String, Object>>) mapData.get("items")).listIterator();
    	while (it.hasNext()) {
    	    Map<String, Object> item = it.next();
    	    if (eqauls(item, module, model)) {
    	    	List<String> files = (List<String>) item.get("files");
    	    	for (String filePath : files) {
    	    		File file = new File(projDir, filePath);
    	    		File fileParent = file.getParentFile();
    	    		FileUtils.deleteQuietly(file);
    	    		deleteEmptyDir(fileParent);
				}
    	    	if (dropTable) {
    	    		// drop table
    	    		try {
						execSQL("drop table " + item.get("table"));
					}
					catch (Exception e) {
						log.info("Drop Table Error Caused.", e);
					}
    	    	}
    	        it.remove();
    	        break;
    	    }
    	}
    	writeStore(mapData);
	}

	/**
	 * render FreeMarker Template
	 * @param ftl
	 * @param model
	 * @return
	 */
	public String renderFtl(String ftl, Map<String, ?> model) {
    	try {
	        return FreeMarkerTemplateUtils.processTemplateIntoString(
	        		new Template(ftl, new InputStreamReader(
	        				CodeGenBaseTool.class.getResourceAsStream(ftl),
	        				CharEncoding.UTF_8), fmCfg.getConfiguration()), model);
        } 
    	catch (Exception e) {
    		throw new RuntimeException("Code Generate Render ERROR, [" + e.getMessage() + "].", e);
        }
	}

	// -- 
	// private methods
	private void deleteEmptyDir(File file) {
		if (file.exists() && file.isDirectory() && file.list().length == 0) {
			File fileParent = file.getParentFile();
			FileUtils.deleteQuietly(file);
			deleteEmptyDir(fileParent);
		}
	}

	private boolean eqauls(Map<String, Object> item, String module, String model) {
		return item.get("module").equals(module) && item.get("model").equals(model);
	}
}
